﻿using System.Threading.Tasks;
using System.Collections.Generic;
using System;

namespace BookSleeve
{
    /// <summary>
    /// Commands that involve custom Lua scripts.
    /// </summary>
    /// <remarks>http://redis.io/commands#eval</remarks>
    public interface IEvalCommands
    {
        /// <summary>
        /// Evaluates the given Lua script without returning a result. 
        /// </summary>
        /// <remarks>http://redis.io/commands/eval</remarks>
        Task Eval(int db, string script, string[] keys, string[] args, bool queueJump = false);

        /// <summary>
        /// Evaluates the given Lua script and returns the result as a bool. 
        /// </summary>
        /// <returns>the result of the script execution, as a bool</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        Task<bool> EvalBoolean(int db, string script, string[] keys, string[] args, bool queueJump = false);

        /// <summary>
        /// Evaluates the given Lua script and returns the result as a byte array. 
        /// </summary>
        /// <returns>the result of the script execution, as a byte array</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        Task<byte[]> EvalBytes(int db, string script, string[] keys, string[] args, bool queueJump = false);

        /// <summary>
        /// Evaluates the given Lua script and returns the values of th.
        /// </summary>
        /// <returns>the result of the script execution, as a collection of values</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        Task<byte[][]> EvalMultiBytes(int db, string script, string[] keys, string[] args, bool queueJump = false);

        /// <summary>
        /// Evaluates the given Lua script and returns the result as a string. 
        /// </summary>
        /// <returns>the result of the script execution, as a string</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        Task<string> EvalString(int db, string script, string[] keys, string[] args, bool queueJump = false);

        /// <summary>
        /// Evaluates the given Lua script and returns the result as an array of strings. 
        /// </summary>
        /// <returns>the result of the script execution, as an array of strings</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        Task<string[]> EvalMultiString(int db, string script, string[] keys, string[] args, bool queueJump = false);

        /// <summary>
        /// Evaluates the given Lua script and returns the result as a long. 
        /// </summary>
        /// <returns>the result of the script execution, as a string</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        Task<long> EvalLong(int db, string script, string[] keys, string[] args, bool queueJump = false);

        /// <summary>
        /// Evaluates the given Lua script and returns the fields and values of th.
        /// </summary>
        /// <returns>the result of the script execution, as a collection of keys and values</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        Task<Dictionary<string, byte[]>> EvalHashPairs(int db, string script, string[] keys, string[] args, bool queueJump = false);
    }

    partial class RedisConnection : IEvalCommands
    {
        /// <summary>
        /// Evaluates the given Lua script without returning a result. 
        /// </summary>
        /// <remarks>http://redis.io/commands/eval</remarks>
        public Task Eval(int db, string script, string[] keys, string[] args, bool queueJump)
        {
            RequireRedis25OrHigher();
            var allParams = CombineParams(script, keys, args);
            return ExecuteVoid(RedisMessage.Create(db, RedisLiteral.EVAL, allParams), queueJump);
        }

        /// <summary>
        /// Evaluates the given Lua script and returns the result as a bool. 
        /// </summary>
        /// <returns>the result of the script execution, as a bool</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        public Task<bool> EvalBoolean(int db, string script, string[] keys, string[] args, bool queueJump)
        {
            RequireRedis25OrHigher();
            var allParams = CombineParams(script, keys, args);
            return ExecuteBoolean(RedisMessage.Create(db, RedisLiteral.EVAL, allParams), queueJump);
        }

        /// <summary>
        /// Evaluates the given Lua script and returns the result as a byte array. 
        /// </summary>
        /// <returns>the result of the script execution, as a byte array</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        public Task<byte[]> EvalBytes(int db, string script, string[] keys, string[] args, bool queueJump)
        {
            RequireRedis25OrHigher();
            var allParams = CombineParams(script, keys, args);
            return ExecuteBytes(RedisMessage.Create(db, RedisLiteral.EVAL, allParams), queueJump);
        }

        /// <summary>
        /// Evaluates the given Lua script and returns the values of th. 
        /// </summary>
        /// <returns>the result of the script execution, as a string</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        public Task<byte[][]> EvalMultiBytes(int db, string script, string[] keys, string[] args, bool queueJump = false)
        {
            RequireRedis25OrHigher();
            var allParams = CombineParams(script, keys, args);
            return ExecuteMultiBytes(RedisMessage.Create(db, RedisLiteral.EVAL, allParams), queueJump);
        }

        /// <summary>
        /// Evaluates the given Lua script and returns the result as a string. 
        /// </summary>
        /// <returns>the result of the script execution, as a string</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        public Task<string> EvalString(int db, string script, string[] keys, string[] args, bool queueJump)
        {
            RequireRedis25OrHigher();
            var allParams = CombineParams(script, keys, args);
            return ExecuteString(RedisMessage.Create(db, RedisLiteral.EVAL, allParams), queueJump);
        }

        /// <summary>
        /// Evaluates the given Lua script and returns the result as an array of strings. 
        /// </summary>
        /// <returns>the result of the script execution, as an array of strings</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        public Task<string[]> EvalMultiString(int db, string script, string[] keys, string[] args, bool queueJump)
        {
            RequireRedis25OrHigher();
            var allParams = CombineParams(script, keys, args);
            return ExecuteMultiString(RedisMessage.Create(db, RedisLiteral.EVAL, allParams), queueJump);
        }

        /// <summary>
        /// Evaluates the given Lua script and returns the result as a long. 
        /// </summary>
        /// <returns>the result of the script execution, as a string</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        public Task<long> EvalLong(int db, string script, string[] keys, string[] args, bool queueJump)
        {
            RequireRedis25OrHigher();
            var allParams = CombineParams(script, keys, args);
            return ExecuteInt64(RedisMessage.Create(db, RedisLiteral.EVAL, allParams), queueJump);
        }

        /// <summary>
        /// Evaluates the given Lua script and returns the fields and values of th. 
        /// </summary>
        /// <returns>the result of the script execution, as a string</returns>
        /// <remarks>http://redis.io/commands/eval</remarks>
        public Task<Dictionary<string, byte[]>> EvalHashPairs(int db, string script, string[] keys, string[] args, bool queueJump = false)
        {
            RequireRedis25OrHigher();
            var allParams = CombineParams(script, keys, args);
            return ExecuteHashPairs(RedisMessage.Create(db, RedisLiteral.EVAL, allParams), queueJump);
        }

        #region Private helpers

        private void RequireRedis25OrHigher()
        {
            if (ServerVersion == null)
            {
                return;
            }

            var version = ServerVersion.Major + (ServerVersion.Minor * 0.1);

            if (version < 2.5)
            {
                throw new InvalidOperationException(string.Format("Redis must be version 2.5 or later in order to use Eval operations.  Current version: {0}", version));
            }
        }

        private string[] CombineParams(string script, string[] keys, string[] args)
        {
            var allParams = new string[keys.Length + args.Length + 2];
            allParams[0] = script;
            allParams[1] = keys.Length.ToString();
            Array.Copy(keys, 0, allParams, 2, keys.Length);
            Array.Copy(args, 0, allParams, keys.Length + 2, args.Length);
            return allParams;
        }

        #endregion

        /// <summary>
        /// Commands that involve running custom Lua scripts.
        /// </summary>
        /// <remarks>http://redis.io/commands#eval</remarks>
        public IEvalCommands Evals
        {
            get { return this; }
        }
    }
}
